﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Device.Location;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using System.Xml;
using Microsoft.Live;
using Microsoft.Phone.Net.NetworkInformation;
using Microsoft.Phone.Reactive;
using Microsoft.Phone.Tasks;
using WP7PhoneTracker.BingMaps;
using WP7PhoneTracker.Commanding;
using WP7PhoneTracker.EntityClasses;
using WP7PhoneTracker.Helpers;
using WP7PhoneTracker.Resources;
using NetworkInterface = System.Net.NetworkInformation.NetworkInterface;

namespace WP7PhoneTracker.Model
{
    public class TrackingList_Model : INotifyPropertyChanged
    {
        #region NotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        readonly GeocodeServiceClient _serviceClient = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
        private int _nCount = -1;
        private readonly ReverseGeocodeRequest _reverseGeocodeRequest;

        public TrackingList_Model()
        {
            #region Commanding

            StartCommand = new RelayCommand(BtnStart) {IsEnabled = true};
            StopCommand = new RelayCommand(BtnStop) {IsEnabled = true};
            GetLocationsCommand = new RelayCommand(BtnGetLocations) { IsEnabled = true };
            ExportXMLCommand = new RelayCommand(BtnExportXML) { IsEnabled = true };
            TraceRouteCommand = new RelayCommand(BtnTraceRoute) { IsEnabled = true };
            DeleteAllCommand = new RelayCommand(BtnDeleteAll) { IsEnabled = true };

            #endregion

            CheckIfAssociatedDataExists();

            #region Inicialize ReverseGeocodeRequest
            _reverseGeocodeRequest = new ReverseGeocodeRequest
            {
                Credentials =
                    new Credentials
                    {   // Set the credentials using a valid Bing Maps key
                        ApplicationId =
                            "AtxKuoISgr85FMz7sDUrwonVYiE5NdEgCI7OXRu4Gg7fG7Q0_Sa_bnsHVSGvnKfu"
                    }
            }; 
            #endregion

            Start_Click();

            IsStartButtonEnable = true;
            CarModeIsChecked = true;
        }

        #region Functions

        private void SaveCoordenatesToXmlInIs()
        {
            if (App.Current.LiveSession != null)
            {
                using (var myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (var isoStream = new IsolatedStorageFileStream("wp7phonetracker.xml", FileMode.Create, myIsolatedStorage))
                    {
                        var settings = new XmlWriterSettings { Indent = true };
                        using (var writer = XmlWriter.Create(isoStream, settings))
                        {

                            writer.WriteStartElement("p", "Trackings", "urn:Trackings");

                            foreach (var tracking in Trackings)
                            {
                                writer.WriteStartElement("Tracking");

                                writer.WriteElementString("lat", tracking.Latitude.ToString(CultureInfo.InvariantCulture));

                                writer.WriteElementString("lon", tracking.Longitude.ToString(CultureInfo.InvariantCulture));

                                writer.WriteEndElement();
                            }

                            writer.WriteEndElement();
                            // Ends the document
                            writer.WriteEndDocument();
                            // Write the XML to the file.
                            writer.Flush();
                        }
                    }
                }
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                                                              {
                                                                  if (MessageBox.Show(
                                                                      AppResource.TrackingList5,
                                                                      AppResource.TrackingList7,
                                                                      MessageBoxButton.OKCancel) != MessageBoxResult.OK)
                                                                      return;
                                                                  var client = new LiveConnectClient(App.Current.LiveSession);
                                                                  client.GetCompleted += GetFolderPropertiesCompleted;
                                                                  // If you put photo to folder it becomes album. 
                                                                  client.GetAsync("me/skydrive/files?filter=folders,albums");
                                                              }); 
            }
            else
            {
                Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show(AppResource.Configuration01, AppResource.warning, MessageBoxButton.OK)); 
            }
        }

        private void SaveToSkyDrive()
        {
            IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication();

            if (!iso.FileExists("wp7phonetracker.xml")) return;
            IsolatedStorageFileStream stream = iso.OpenFile("wp7phonetracker.xml", FileMode.Open);
            var uploadClient = new LiveConnectClient(App.Current.LiveSession);
            uploadClient.UploadCompleted += UploadClientUploadCompleted;
            uploadClient.UploadAsync(App.Current.SkyDriveFolderId, String.Format("wp7phonetracker{0}.txt", String.Format("{0}{1}{2}", DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second)), stream);
        }

        private void GetFolderPropertiesCompleted(object sender, LiveOperationCompletedEventArgs e)
        {
            var client = new LiveConnectClient(App.Current.LiveSession);
            if (e.Error == null)
            {
                var folderData = (Dictionary<string, object>)e.Result;
                var folders = (List<object>)folderData["data"];

                foreach (
                    var folder in
                        folders.Cast<Dictionary<string, object>>().Where(
                            folder => folder["name"].ToString() == App.Current.SkyDriveFolderName))
                    App.Current.SkyDriveFolderId = folder["id"].ToString();

                if (App.Current.SkyDriveFolderId == string.Empty)
                {
                    var skyDriveFolderData = new Dictionary<string, object> {{"name", App.Current.SkyDriveFolderName}};
                    client.PostCompleted += CreateFolderCompleted;
                    client.PostAsync("me/skydrive", skyDriveFolderData);
                }
                else
                    SaveToSkyDrive();
            }
            else
            {
                Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show(e.Error.Message, AppResource.info, MessageBoxButton.OK)); 
            }
        }

        static void CreateFolderCompleted(object sender, LiveOperationCompletedEventArgs e)
        {
            //if (e.Error == null)
            //{
            //    var text = "Folder created.";
            //}
            //else
            //{
            //    var text = "Error calling API: " + e.Error.ToString();
            //}
        }

        static void UploadClientUploadCompleted(object sender, LiveOperationCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show(AppResource.TrackingList6,
                                                                                AppResource.TrackingList7,
                                                                                MessageBoxButton.OK));
            }
        }

        #region GetTrackings

        private void GetTrackings()
        {
            Trackings = new ObservableCollection<Tracking>
                (
                (from result0 in
                     CrudTracking.GetTrackings()
                 where
                     result0.TrackingNameId ==
                     ((App) Application.Current).
                         SelectedTrackingName.Id
                 select result0).OrderByDescending(
                     ress => ress.DtRegistered));
        }

        #endregion

        #region ReverseGeocode

        private void ReverseGeocode()
        {
            _nCount++;

            var point = new Location {Latitude = Trackings[_nCount].Latitude, Longitude = Trackings[_nCount].Longitude};
            _reverseGeocodeRequest.Location = point;

            _serviceClient.ReverseGeocodeAsync(_reverseGeocodeRequest);
            _serviceClient.ReverseGeocodeCompleted += ServiceClientReverseGeocodeCompleted;
        }

        void ServiceClientReverseGeocodeCompleted(object sender, ReverseGeocodeCompletedEventArgs e)
        {
            _serviceClient.ReverseGeocodeCompleted -= ServiceClientReverseGeocodeCompleted;

            try
            {
                if (e.Error != null) return;
                if (e.Result == null) return;
                var result = e.Result.Results[0];

                if (result.DisplayName.Split(',').Count() ==3)
                {
                    Trackings[_nCount].LocationName = result.DisplayName.Split(',')[0];
                    Trackings[_nCount].CityName = result.DisplayName.Split(',')[1];
                    Trackings[_nCount].CountryName = result.DisplayName.Split(',')[2]; 
                }
                else
                {
                    Trackings[_nCount].CityName = result.DisplayName.Split(',')[0];
                    Trackings[_nCount].CountryName = result.DisplayName.Split(',')[1]; 
                }

                CrudTracking.UpdateTracking(Trackings[_nCount]);

                GetTrackings();

                if (_nCount >= (Trackings.Count - 1)) return;
                _nCount++;

                var point = new Location
                                {Latitude = Trackings[_nCount].Latitude, Longitude = Trackings[_nCount].Longitude};
                _reverseGeocodeRequest.Location = point;

                _serviceClient.ReverseGeocodeAsync(_reverseGeocodeRequest);
                _serviceClient.ReverseGeocodeCompleted += ServiceClientReverseGeocodeCompleted;

            }
            catch (Exception ex)
            {
                var msg = ex.Message;
            }
        } 

        #endregion

        #region CheckIfAssociatedDataExists

        private void CheckIfAssociatedDataExists()
        {
            var result = CrudTracking.GetTrackings().ToList();

            if (result.Count == 0) return;
            var res = (from dt in result
                       where dt.TrackingNameId == App.Current.SelectedTrackingName.Id
                       select dt).OrderByDescending(ress => ress.DtRegistered);
            if (!res.Any()) return;
            Trackings = new ObservableCollection<Tracking>(res);
            IsReverserGeoCodeButtonEnable = true;
        } 

        #endregion

        #region GPS

        private GeoCoordinateWatcher _watcher;

        // Event handler for location data. This invokes code on the
        // page's main thread.
        private void WatcherPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            try
            {
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                                                              {
                                                                  var tracking = new Tracking
                                                                                     {
                                                                                         Latitude =
                                                                                             e.Position.Location.
                                                                                             Latitude,
                                                                                         Longitude =
                                                                                             e.Position.Location.
                                                                                             Longitude,
                                                                                         DtRegistered =
                                                                                             e.Position.Timestamp.
                                                                                             DateTime,
                                                                                         Speed =
                                                                                             e.Position.Location.Speed,
                                                                                         Altitude =
                                                                                             e.Position.Location.
                                                                                             Altitude,
                                                                                         Course =
                                                                                             e.Position.Location.Course,
                                                                                         VerticalAccuracy =
                                                                                             e.Position.Location.
                                                                                             VerticalAccuracy,
                                                                                         HorizontalAccuracy =
                                                                                             e.Position.Location.
                                                                                             HorizontalAccuracy
                                                                                     };

                                                                  if (Trackings.Count > 0)
                                                                  {
                                                                      if (Trackings[Trackings.Count - 1].Latitude !=
                                                                          tracking.Latitude
                                                                          &&
                                                                          Trackings[Trackings.Count - 1].Longitude !=
                                                                          tracking.Longitude)
                                                                      {
                                                                          var sCoord = new GeoCoordinate(Trackings[Trackings.Count - 1].Latitude, Trackings[Trackings.Count - 1].Longitude);
                                                                          var eCoord = new GeoCoordinate(tracking.Latitude, tracking.Longitude);
                                                                          var distanceResult = eCoord.GetDistanceTo(sCoord);
                                                                          //if (distanceResult > 25)
                                                                          {
                                                                              tracking.TrackingNameId = App.Current.SelectedTrackingName.Id;
                                                                              var res = CrudTracking.AddTracking(tracking);

                                                                              if (!res) return;
                                                                              GetTrackings(); 
                                                                          }
                                                                      }
                                                                  }
                                                                  else
                                                                  {
                                                                      tracking.TrackingNameId = App.Current.SelectedTrackingName.Id;
                                                                      var res = CrudTracking.AddTracking(tracking);
                                                                      if (!res) return;
                                                                      GetTrackings();
                                                                  }
                                                              });
            }
            catch
            {
            }
        }

        // Event handler for the GeoCoordinateWatcher.StatusChanged event.
        private void _watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
            switch (e.Status)
            {
                case GeoPositionStatus.Disabled:
                    // The Location Service is disabled or unsupported.
                    // Check to see whether the user has disabled the Location Service.
                    if (_watcher.Permission == GeoPositionPermission.Denied)
                    {
                        // The user has disabled the Location Service on their device.
                        //statusTextBlock.Text = "you have this application access to location.";
                    }
                    else
                    {
                        //statusTextBlock.Text = "location is not functioning on this device";
                    }
                    break;

                case GeoPositionStatus.Initializing:
                    // The Location Service is initializing.
                    // Disable the Start Location button.
                    //startLocationButton.IsEnabled = false;
                    break;

                case GeoPositionStatus.NoData:
                    // The Location Service is working, but it cannot get location data.
                    // Alert the user and enable the Stop Location button.
                    //statusTextBlock.Text = "location data is not available.";
                    //stopLocationButton.IsEnabled = true;
                    break;

                case GeoPositionStatus.Ready:
                    // The Location Service is working and is receiving location data.
                    // Show the current position and enable the Stop Location button.
                    //statusTextBlock.Text = "location data is available.";
                    //stopLocationButton.IsEnabled = true;
                    break;
            }
        }

        private void Start_Click()
        {
            // First, handle the case where emulation is not being used.
            {
                // Initialize the GeoCoordinateWatcher.
                _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High) // using high accuracy
                               {
                                   #region MovementThreshold
                                   // I set GeoCoordinateWatcher’s MovementThreshold to 10 meters, so if you’re driving, you’ll receive 
                                   //map updates once a second or so, but if you’re walking, updates will be less frequent. You can lower 
                                   //the threshold to generate more frequent updates, but remember that lower MovementThresholds consume 
                                   //battery power faster. A fun addition to the app would be a slider that allows the user to set the 
                                   //MovementThreshold. That way, a user who is driving could select a rather high value, while a user 
                                   //out for a morning run could select something finer.
                                   #endregion
                                   MovementThreshold = App.Current.MovementThreshold// use MovementThreshold to ignore noise in the signal
                               }; 

                // Reactive Extensions uses Observable sequences to represent data streams.
                // Create an Observable sequence from the event stream using the FromEvent method.
                // This method uses the .NET Generic syntax to specify the type of event args for the event.
                // The parameters to the method are the add and remove handlers of the GeoCoordinateWatcher object.
                IObservable<IEvent<GeoPositionChangedEventArgs<GeoCoordinate>>> positionEventAsObservable =
                    Observable.FromEvent<GeoPositionChangedEventArgs<GeoCoordinate>>(
                        ev => _watcher.PositionChanged += ev,
                        ev => _watcher.PositionChanged -= ev);
                _watcher.StatusChanged += _watcher_StatusChanged;

                // Subscribe to the observable data stream. You can use the same event handler as if you 
                // were using the GeoCoordinateWatcher.PositionChanged event directly.
                IDisposable positionSubscription = positionEventAsObservable.Subscribe(
                    args => WatcherPositionChanged(args.Sender, args.EventArgs));

            }
        }

        public void Stop_Click()
        {
            if (_watcher != null) _watcher.Stop();
            if (Trackings.Count > 0)
                IsReverserGeoCodeButtonEnable = true;
        }

        #endregion

        #endregion

        #region Properties

        #region Trackings

        private ObservableCollection<Tracking> _trackings;
        public ObservableCollection<Tracking> Trackings
        {
            get { return _trackings ?? (_trackings = new ObservableCollection<Tracking>()); }
            set
            {
                _trackings = value;
                NotifyPropertyChanged("Trackings");
            }
        }

        #endregion

        #region IsNetworkAvailable

        #region IsNetworkAvailable

        private bool _isNetworkAvailable;
        public bool IsNetworkAvailable
        {
            get { return _isNetworkAvailable; }
            set { _isNetworkAvailable = value; NotifyPropertyChanged("IsNetworkAvailable"); }
        }

        #endregion

        #region IsStartButtonEnable

        private bool _isStartButtonEnable;
        public bool IsStartButtonEnable
        {
            get { return _isStartButtonEnable; }
            set { _isStartButtonEnable = value; NotifyPropertyChanged("IsStartButtonEnable"); }
        }

        #endregion

        #endregion

        #region IsReverserGeoCodeButtonEnable
        private bool _isReverserGeoCodeButtonEnable;
        public Boolean IsReverserGeoCodeButtonEnable
        {
            get { return _isReverserGeoCodeButtonEnable; }
            set
            {
                _isReverserGeoCodeButtonEnable = value;
                NotifyPropertyChanged("IsReverserGeoCodeButtonEnable");
            }
        } 
        #endregion

        #region CarModeIsChecked
        private bool _carModeIsChecked;
        public Boolean CarModeIsChecked
        {
            get { return _carModeIsChecked; }
            set
            {
                _carModeIsChecked = value;
                if (_carModeIsChecked)
                {
                    WalkModeIsChecked = false;
                    App.Current.MovementThreshold = 20;
                }
                NotifyPropertyChanged("CarModeIsChecked");
            }
        }
        #endregion

        #region WalkModeIsChecked
        private bool _walkModeIsChecked;
        public Boolean WalkModeIsChecked
        {
            get { return _walkModeIsChecked; }
            set
            {
                _walkModeIsChecked = value;
                if (_walkModeIsChecked)
                {
                    CarModeIsChecked = false;
                    App.Current.MovementThreshold = 3;
                }
                NotifyPropertyChanged("WalkModeIsChecked");
            }
        } 
        #endregion

        #endregion

        #region Commanding

        #region StartCommand
        public ICommand StartCommand { get; private set; }
        private void BtnStart()
        {
            IsStartButtonEnable = false;
            // Start the GeoCoordinateWatcher to begin receiving data from the Location Service.
            _watcher.Start();
            IsReverserGeoCodeButtonEnable = false;
        }

        #endregion

        #region StopCommand
        public ICommand StopCommand { get; private set; }
        private void BtnStop()
        {
            IsStartButtonEnable = true;
            Stop_Click();
        } 
        #endregion

        #region GetLocationsCommand

        public ICommand GetLocationsCommand { get; private set; }
        private void BtnGetLocations()
        {
            if (Trackings.Count <= 0) return;
            if (NetworkInterface.GetIsNetworkAvailable())
            {
                _nCount = -1;
                ReverseGeocode(); 
            }
            else
            {
                Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show(AppResource.TrackingList8, AppResource.info, MessageBoxButton.OK)); 
            }
        }

        #endregion

        #region ExportXMLCommand

        public ICommand ExportXMLCommand { get; private set; }
        private void BtnExportXML()
        {
            if (Trackings.Count <= 0) return;
            if (NetworkInterface.GetIsNetworkAvailable())
                SaveCoordenatesToXmlInIs();
            else
                Deployment.Current.Dispatcher.BeginInvoke(
                    () => MessageBox.Show(AppResource.TrackingList8, AppResource.info, MessageBoxButton.OK));
        }

        #endregion

        #region TraceRouteCommand

        public ICommand TraceRouteCommand { get; private set; }
        private void BtnTraceRoute()
        {
            if (Trackings.Count <= 0) return;
            var ending = Trackings[(Trackings.Count() - 1)];
            var beginig = Trackings[0];
            var ini = new GeoCoordinate(beginig.Latitude, beginig.Longitude, beginig.Altitude, beginig.HorizontalAccuracy, beginig.VerticalAccuracy, beginig.Speed, beginig.Course);
            var end = new GeoCoordinate(ending.Latitude, ending.Longitude, ending.Altitude, ending.HorizontalAccuracy, ending.VerticalAccuracy, ending.Speed, ending.Course);
            var directionTask = new BingMapsDirectionsTask
                                    {
                                        Start = new LabeledMapLocation(AppResource.TrackingList9, ini),
                                        End = new LabeledMapLocation(AppResource.TrackingList10, end),
                                    };
            directionTask.Show();
        }

        #endregion

        #region DeleteAllCommand

        public ICommand DeleteAllCommand { get; private set; }
        private void BtnDeleteAll()
        {
            if (CrudTracking.DeleteAllTracking(Trackings))
                Trackings.Clear();
        }

        #endregion

        #endregion
    }
}